觀察 index-Start.html,裡面有一個作者幫我們準備好的Debounce
函式,這是什麼呢?在解釋它之前要先來説説本日的重點,看題目名稱跟挖空的圖片應該就知道要透過滾輪滑動讓圖片滑進滑出,這就要用到Document: scroll event。
舉例來說,如果我們針對視窗添加事件監聽器偵測滑鼠滾動事件,並且用console.count("scroll")
印出觸發次數,那麼你會發現輕輕一滑,你就觸發了 4~50 次,所以假設你的觸發後要執行的函式邏輯非常複雜,那麼將影響效能,
function handleScroll(e) {
console.count("scroll");
}
document.addEventListener("scroll", handleScroll);
這個時候我們就要借用到Debounce & Throttle
,我們在真正要執行的函式外面在包裹一層限制器,去限制他觸發的頻率(雖然也是每次滾動會觸發多次這個限制器函式,但裡面只做了簡單的狀態判斷,只有符合設定時才會執行真正要做的主邏輯),詳細的介紹可以看這篇的例子Debounce & Throttle - Alex Ian。
function debounce(func, wait = 20, immediate = true) {
var timeout;
//回傳包裹後的函式
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
於是我們宣告一個變數,並且將 handleScroll 包一層作者給的限制器
const debounceScroll = debounce(handleScroll);
接著我們將監聽器改帶入著個帶有限制器的 eventhandler 函式,你會發現只有在一開始滾動會觸發及停止滾動的最後才會再次觸發,那是因為判斷中有一句var callNow = immediate && !timeout;
只有當停止滾動沒有 setTimout 後才會為true
才能再次觸發,持續滾動則會一直重置這wait=20
毫秒的 timeout 造成不會執行真正的邏輯,藉此能達到限制觸發的效果
document.addEventListener("scroll", debounceScroll);
前置作業終於完成,我們要開始處理照片的滑入滑出效果,這個經過前 10 天的各種節點操作事件監聽練習,相對上面的去抖動簡單許多了,很容易就能聯想到是要我們滑動到個別照片的特定位置時添加、移除 CSS 中的已經寫好的active
class,實作開始
const imgList = document.querySelectorAll("img");
改寫 handleScroll 函式裡面真正要執行的邏輯,我們先定義要掛上 class 的位置為圖片露出一半並且,滾動超過圖片底部時須移除 class。
slideInAt
:計算各圖片該滑入位置。window.scrollY
表示視窗垂直方向上已經滾動的距離,window.innerHeight
表示當前瀏覽器窗口的內部高度,img.height
則是圖片的高度。透過這三個數值算出當圖片的一半進入視窗時的位置。imageBottom
:計算圖片底部的位置。img.offsetTop
表示圖片頂部相對於其父容器的上邊緣的距離,加上img.height
(圖片的高度)就得到了圖片的底部位置。isHalfShown
:用來判斷是否有超過一半的圖片進入了視窗。如果slideInAt
大於img.offsetTop
,則表示圖片的一半已經進入了視窗,此時isHalfShown
設置為true
。isNotScrolledPast
:用來判斷是否有圖片的一部分還沒有完全滾出視窗。如果window.scrollY
(當前滾動位置)小於 imageBottom
(圖片底部位置),則表示圖片還在視窗內,此時 isNotScrolledPast
設置為true
。isHalfShown
和 isNotScrolledPast
都為true
,表示圖片的一半已經進入視窗且還未完全滾出視窗,那個就掛上active
這個 class name,反之則移除。function handleScroll(e) {
//觸發時每張圖片都要馬上判斷
imgList.forEach((img) => {
// 圖片滑入位置
const slideInAt = window.scrollY + window.innerHeight - img.height / 2;
// 圖片底部於視窗位置
const imageBottom = img.offsetTop + img.height;
//圖片是否已經露出一半於畫面中
const isHalfShown = slideInAt > img.offsetTop;
//滾動距離還沒超過圖片最底部(是否還看得到一點點圖片)
const isNotScrolledPast = window.scrollY < imageBottom;
//兩個判斷都成立時,掛上class name
if (isHalfShown && isNotScrolledPast) {
img.classList.add("active");
//反之拔掉class name
} else {
img.classList.remove("active");
}
});
}